import java.awt.Point;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;

/**
 * The back end that handles processing of the Sudoku game arguments.
 * Initialises aspects of the game given selected parameters
 */
public class SudokuBackEnd
{
	GridInterface gridInterface;
	int[][] initialGrid;
	int[][] lockedGrid;
	int[][] solvedGrid;
	int[][] partialSolvedGrid;
	
	public Observer numberEntered = new Observer()
	{
		
		@Override
		public void update(Observable o, Object arg)
		{
			if (arg instanceof Point)
			{
				numberEntered((Point)arg);
			}
		}
	};
	
	/**
	 * Obtains methods for game cell manipulation
	 * @param gridInterface Interface of methods concerning operations on cells.
	 */
	public SudokuBackEnd(GridInterface gridInterface)
	{
		this.gridInterface = gridInterface;
	}
	
	/**
	 * A game is created based on difficulty setting selected by user. 
	 * Difficulty in Sudoku is determined by number of locked cells visible to user at start of game. 
	 * A call is made to create a solution grid first.
	 * This grid is then re-created until a unique solution exists. 
	 * Randomly displays a number of locked cells on grid equivalent to difficulty setting. 
	 * @param difficulty Difficulty setting selected
	 */
	public void generateBoard(Difficulty difficulty)
	{
		boolean[][] seeds = new boolean[SudokuFrontEnd.colCount][SudokuFrontEnd.rowCount];
		ArrayList<Point> shownCells;
		boolean uniqueSolution = false;
		Random r = new Random();
		int numberShown = 35;
		int numFilled = r.nextInt(40) + 41;
		solvedGrid = new int[SudokuFrontEnd.colCount][SudokuFrontEnd.rowCount];
		lockedGrid = new int[SudokuFrontEnd.colCount][SudokuFrontEnd.rowCount];
		
		switch (difficulty)
		{
			case Easy:
				numberShown = 45;
				break;
			case Medium:
				numberShown = 35;
				break;
			case Hard:
				numberShown = 25;
				break;
			case VeryHard:
				numberShown = 19;
				break;
		}
		
		//If the game is not a completed grid (debugging) and no unique solution exists,
		//a random grid continually be generated until it is unique
		while (solvedGrid == null || !uniqueSolution)
		{
			shownCells = new ArrayList<Point>();
			initialGrid = SudokuLibrary.generateRandomGrid(numFilled);
			uniqueSolution = SudokuLibrary.oneUniqueSolution(0, initialGrid);
			if (uniqueSolution)
			{
				solvedGrid = SudokuLibrary.solve(0, initialGrid);
				for (int i = 0; i < SudokuFrontEnd.cellCount; i++)
				{
					shownCells.add(new Point(i % SudokuFrontEnd.colCount, i / SudokuFrontEnd.rowCount));
				}
				Point k;
				for (int i = 0; i < numberShown; i++)
				{
					k = shownCells.get(r.nextInt(shownCells.size()));
					shownCells.remove(k);
					lockedGrid[k.x][k.y] = solvedGrid[k.x][k.y];
					seeds[k.x][k.y] = true;
				}
				gridInterface.setCellsLocked(seeds);
				
				if (uniqueSolution && solvedGrid != null)
				{
					gridInterface.setCellsData(lockedGrid);
				}
			}
		}
	}
	
	/**
	 * Attempts to create a grid from its saved status.
	 * Locked cells and filled cells are given.
	 * Since a unique solution exists, the solution grid can be generated from these values.
	 * A check is done to see if the loaded values are valid, i.e. a unique solution exists
	 * @param givenCells Locked cells visible to user at start of game
	 * @param allFilledCells All cells currently visible to user, may contain user values
	 * @return whether load state was successful
	 */
	public boolean loadGame(boolean[][] givenCells, int[][] allFilledCells)
	{
		initialGrid = new int[SudokuFrontEnd.colCount][SudokuFrontEnd.rowCount];
		partialSolvedGrid = new int[SudokuFrontEnd.colCount][SudokuFrontEnd.rowCount];
		boolean[][] cellsLocked = new boolean[SudokuFrontEnd.colCount][SudokuFrontEnd.rowCount];
		Point cell;
		for (int i = 0; i < SudokuFrontEnd.cellCount; i++)
		{
			if (givenCells[i % SudokuFrontEnd.colCount][i / SudokuFrontEnd.rowCount] == true)
			{
				initialGrid[i % SudokuFrontEnd.colCount][i / SudokuFrontEnd.rowCount] = allFilledCells[i % SudokuFrontEnd.colCount][i / SudokuFrontEnd.rowCount];
				cellsLocked[i % SudokuFrontEnd.colCount][i / SudokuFrontEnd.rowCount] = true;
			}
			else
			{
				cellsLocked[i % SudokuFrontEnd.colCount][i / SudokuFrontEnd.rowCount] = false;
			}
			if (allFilledCells[i % SudokuFrontEnd.colCount][i / SudokuFrontEnd.rowCount] != 0)
			{
				partialSolvedGrid[i % SudokuFrontEnd.colCount][i / SudokuFrontEnd.rowCount] = allFilledCells[i % SudokuFrontEnd.colCount][i / SudokuFrontEnd.rowCount];
			}
			
		}
		//Creates a solition based on given values fom saved game.
		//Checks for validty of loaded values 
		if (SudokuLibrary.oneUniqueSolution(0, initialGrid))
		{
			solvedGrid = SudokuLibrary.solve(0, initialGrid);
			for (int i = 0; i < SudokuFrontEnd.cellCount; i++)
			{
				cell = new Point(i % SudokuFrontEnd.colCount, i / SudokuFrontEnd.rowCount);
				gridInterface.setCellData(partialSolvedGrid[i % SudokuFrontEnd.colCount][i / SudokuFrontEnd.rowCount], cell);
				if (cellsLocked[i % SudokuFrontEnd.colCount][i / SudokuFrontEnd.rowCount])
				{
					gridInterface.setCellLocked(true, cell);
				}
				if (SudokuLibrary.isWin(gridInterface.getCellsData()))
				{
					for (int j = 0; j < SudokuFrontEnd.cellCount; j++)
					{
						Point p = new Point(j % SudokuFrontEnd.rowCount, j / SudokuFrontEnd.colCount);
						gridInterface.setCellHighlighted(true, p);
					}
				}
				else
				{
					if (SudokuLibrary.isLegal(cell, gridInterface.getCellData(cell), gridInterface.getCellsData()))
					{
						gridInterface.setCellError(false, cell);
					}
					else
					{
						gridInterface.setCellError(true, cell);
					}
				}
			}
			return true;
		}
		return false;
	}
	
	/**
	 * Performs actions based on operations done to selected cell.
	 * The game ends if the number fills the Grid and solution is valid. All cells highlighted.
	 * The error state of a cell is changed based on whether the entered number is legal.
	 * @param cell Cell inside Sudoku grid being updated
	 */
	public void numberEntered(Point cell)
	{
		//Colours cells according to win state if all cels filled
		if (SudokuLibrary.isWin(gridInterface.getCellsData()))
		{
			for (int i = 0; i < SudokuFrontEnd.cellCount; i++)
			{
				Point p = new Point(i % SudokuFrontEnd.rowCount, i / SudokuFrontEnd.colCount);
				gridInterface.setCellHighlighted(true, p);
			}
		}
		//Otherwise no highlight
		else
		{
			for (int i = 0; i < SudokuFrontEnd.cellCount; i++)
			{
				Point p = new Point(i % SudokuFrontEnd.rowCount, i / SudokuFrontEnd.colCount);
				gridInterface.setCellHighlighted(false, p);
			}
		}
		//Updates cell error status based on value legality
		for (int i = 0; i < SudokuFrontEnd.cellCount; i++)
		{
			Point p = new Point(i % SudokuFrontEnd.rowCount, i / SudokuFrontEnd.colCount);

			if (SudokuLibrary.isLegal(p, gridInterface.getCellData(p), gridInterface.getCellsData()))
			{
				gridInterface.setCellError(false, p);
			}
			else
			{
				gridInterface.setCellError(true, p);
			}
		}
	}
	
	
	/**
	 * A hint is given to the user, by filling an unfilled cell for the user. 
	 * This only applies if all current filled cells contain legal values for the game.
	 */
	public void giveHint()
	{
		int[][] currentGrid = gridInterface.getCellsData();
		Cell[][] currentCells = gridInterface.getCells();
		boolean hasError = false;
		//Checks for any errors in current game
		for (int i = 0; i < SudokuFrontEnd.cellCount && hasError == false; i++)
		{
			if (currentCells[i % SudokuFrontEnd.colCount][i / SudokuFrontEnd.rowCount].isError())
			{
				hasError = true;
			}
		}
		//If there is no error so far and grid contains unfilled cells, 
		//a random number will be filled for the user in a valid free cell
		if (hasError == false && solvedGrid != null && !SudokuLibrary.isWin(gridInterface.getCellsData()))
		{
			Random r = new Random();
			
			ArrayList<Point> emptyPoints = new ArrayList<Point>();
			for (int i = 0; i < SudokuFrontEnd.cellCount; i++)
			{
				Point p = new Point(i % SudokuFrontEnd.colCount, i / SudokuFrontEnd.rowCount);
				if (currentGrid[p.x][p.y] == 0)
				{
					emptyPoints.add(p);
				}
			}
			Point chosenPoint = emptyPoints.get(r.nextInt(emptyPoints.size()));
	    	gridInterface.setCellData(solvedGrid[chosenPoint.x][chosenPoint.y], chosenPoint);
		}
	}
}
 